package com.joeshing.security.authentication;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;


public class CacheAnonymousAuthenticationFilter extends GenericFilterBean {

   //~ Instance fields ================================================================================================

   private AuthenticationDetailsSource authenticationDetailsSource;
   private String key = "anonymous_";
   private List<GrantedAuthority> grantedAuthoritys = new ArrayList<GrantedAuthority>();

   //~ Methods ========================================================================================================


   @Override
   protected void initFilterBean() throws ServletException {
	   super.initFilterBean();
   	   Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
       Assert.hasLength(key);
       GrantedAuthority grantedAuthority = new GrantedAuthorityImpl("AUTH_ANONYMOUS");
       grantedAuthoritys.add(grantedAuthority);
   }

   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
           throws IOException, ServletException {

       if (applyAnonymousForThisRequest((HttpServletRequest) req)) {
           if (SecurityContextHolder.getContext().getAuthentication() == null) {
               SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req));

               if (logger.isDebugEnabled()) {
                   logger.debug("Populated SecurityContextHolder with anonymous token: '"
                       + SecurityContextHolder.getContext().getAuthentication() + "'");
               }
           } else {
               if (logger.isDebugEnabled()) {
                   logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
                       + SecurityContextHolder.getContext().getAuthentication() + "'");
               }
           }
       }

       chain.doFilter(req, res);
   }

/**
    * Enables subclasses to determine whether or not an anonymous authentication token should be setup for
    * this request. This is useful if anonymous authentication should be allowed only for specific IP subnet ranges
    * etc.
    *
    * @param request to assist the method determine request details
    *
    * @return <code>true</code> if the anonymous token should be setup for this request (provided that the request
    *         doesn't already have some other <code>Authentication</code> inside it), or <code>false</code> if no
    *         anonymous token should be setup for this request
    */
   protected boolean applyAnonymousForThisRequest(HttpServletRequest request) {
       return true;
   }

   protected Authentication createAuthentication(HttpServletRequest request) {
	   SessionAuthenticationDetails details = (SessionAuthenticationDetails)authenticationDetailsSource.buildDetails(request);
       AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key, key+details.getSessionId(),grantedAuthoritys);
       auth.setDetails(details);
       return auth;
   }

   public String getKey() {
       return key;
   }

   public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) {
       Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
       this.authenticationDetailsSource = authenticationDetailsSource;
   }

   public void setKey(String key) {
       this.key = key;
   }

}
